use app_service::AppService;
use axum::Router;
use axum::extract::DefaultBodyLimit;
use my_rust_core::shared::app_config_service;
use shared::my_database;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::signal;
use tower::ServiceBuilder;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;

mod app_service;
mod bootstrap;
mod entities;
mod handlers;
mod middleware;
mod routes;
mod security;
mod services;
mod shared;

#[tokio::main]
pub async fn start() {
    setup_logger();

    let api_routes = routes::routing::get_routes();
    let server_config = &app_config_service::APP_CONFIG.server;

    let db = my_database::get_connection()
        .await
        .expect("Failed to establish database connection");

    let app_service = Arc::new(AppService::new(Arc::new(db)));

    bootstrap::entry::init(app_service.clone())
        .await
        .expect("Database init failed");

    // IMPORTANT!!!
    // Want to apply with_state without error you must make sure each setting routes functions return the same date type as your state, here is Router<Arc<AppState>>.
    let app = Router::new()
        .nest(&server_config.route_root_group_name, api_routes)
        .layer(
            ServiceBuilder::new()
                .layer(axum::middleware::from_fn_with_state(
                    app_service.clone(),
                    middleware::auth::check_auth,
                ))
                .layer(DefaultBodyLimit::max(
                    server_config.request_body_limit * 1024 * 1024,
                ))
                .layer(TraceLayer::new_for_http())
                // Graceful shutdown will wait for outstanding requests to complete. Add a timeout so
                // requests don't hang forever.
                .layer(TimeoutLayer::new(Duration::from_secs(10))),
        )
        .with_state(app_service)
        .into_make_service_with_connect_info::<SocketAddr>();

    let listener = tokio::net::TcpListener::bind(format!(
        "{}:{}",
        &server_config.tcp_listener_bind_address, &server_config.port
    ))
    .await
    .unwrap();

    tracing::info!(
        "listening on {}{}",
        listener.local_addr().unwrap(),
        server_config.route_root_group_name
    );

    // This code will only compile and run in debug builds.
    #[cfg(debug_assertions)]
    {
        println!(
            "listening on {}{}",
            listener.local_addr().unwrap(),
            server_config.route_root_group_name
        );
    }

    // axum::serve(listener, app).await.unwrap();

    // Run the server with graceful shutdown
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }
}

fn setup_logger() {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::DEBUG)
        .with_test_writer()
        .init();

    // let debug_log = Arc::new(File::create("debug.log").unwrap());

    // let mk_writer = std::io::stderr.with_max_level(Level::ERROR).or_else(
    //     std::io::stdout
    //         .with_max_level(Level::INFO)
    //         .and(debug_log.with_max_level(Level::DEBUG)),
    // );

    // tracing_subscriber::fmt().with_writer(mk_writer).init();

    // let info_file = rolling::daily("./logs", "info").with_max_level(tracing::Level::INFO);

    // tracing_subscriber::fmt()
    //     .with_writer(info_file)
    //     .with_ansi(false)
    //     .finish();

    // // Enables tracing.
    // tracing_subscriber::registry()
    //     .with(
    //         tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
    //             "api_graceful_shutdown=debug,tower_http=debug,axum=trace".into()
    //         }),
    //     )
    //     .with(tracing_subscriber::fmt::layer().without_time())
    //     .init();
}
